﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Threading;

namespace MyEmulators2
{
        #region Delegates

        //Sends progress updates
        public delegate void ImportProgressHandler(int percentDone, int taskCount, int taskTotal, string taskDescription);

        //Sends item update events
        public delegate void RomStatusChangedHandler(object obj, ImportAction action);
        
        #endregion


    //Scans the file system and attempts to retrieve details for any new items.
    //The importer is based on code from MovingPictures. Credits to the MovingPictures team!
    class Importer
    {
        ScraperProvider scraperProvider = null;
        internal ScraperProvider ScraperProvider
        {
            get { return scraperProvider; }
        }

        bool isBackground = false;

        int threadCount = 5;
        volatile bool firstRun = true;

        //used to synchronise Start() and Stop()
        readonly object syncRoot = new object();

        //Used to store a complete list of roms currently importing
        Dictionary<int, RomMatch> lookupMatch;
        readonly object lookupSync = new object();
        ArrayList allMatches;

        List<Thread> importerThreads;
        volatile int percentDone = 0;

        volatile bool pause = false;

        #region Constructors

        public Importer()
        {
            init();
        }

        /// <summary>
        /// Creates a new Importer with the specified background state.
        /// </summary>
        /// <param name="isBackground">
        /// If true the importer will start downloading data immediately, else the importer will
        /// return a list of files to import and await a call to StartRetrieving()
        /// </param>
        public Importer(bool isBackground)
        {
            this.isBackground = isBackground;
            init();
        }

        #endregion

        #region Events

        //Sends progress updates
        public event ImportProgressHandler Progress;

        //Sends item update events
        public event RomStatusChangedHandler RomStatusChanged;

        #endregion

        #region Public Properties

        public bool Paused
        {
            get { return pause; }
        }

        volatile bool doWork = false;
        public bool DoWork
        {
            get { return doWork; }
            set { doWork = value; }
        }

        volatile bool importReady = false;
        /// <summary>
        /// False if the importer has not yet returned a list of files to import
        /// and cannot start retrieving or accept manual input (i.e. Start() has not been called)
        /// </summary>
        public bool Ready
        {
            get { return importReady; }
        }

        /// <summary>
        /// True if the importer is currently downloading info
        /// </summary>
        volatile bool importStarted = false;
        public bool Started
        {
            get { return importStarted; }
        }

        // Matches that have not yet been scanned.
        public ArrayList PendingMatches
        {
            get { return ArrayList.ReadOnly(pendingMatches); }
        } private ArrayList pendingMatches;

        // Same as PendingMatches, but this list gets priority. Used for user based interaction.
        public ArrayList PriorityPendingMatches
        {
            get { return ArrayList.ReadOnly(priorityPendingMatches); }
        } private ArrayList priorityPendingMatches;

        // Matches that are not close enough for auto approval and require user input.
        public ArrayList MatchesNeedingInput
        {
            get { return ArrayList.ReadOnly(matchesNeedingInput); }
        } private ArrayList matchesNeedingInput;

        // Matches that the importer is currently pulling details for
        public ArrayList RetrievingDetailsMatches
        {
            get { return ArrayList.ReadOnly(retrievingDetailsMatches); }
        } private ArrayList retrievingDetailsMatches;

        // Matches that are accepted and are awaiting details retrieval and commital. 
        public ArrayList ApprovedMatches
        {
            get { return ArrayList.ReadOnly(approvedMatches); }
        } private ArrayList approvedMatches;

        // Same as ApprovedMatches but this list get's priority. Used for user based interaction.
        public ArrayList PriorityApprovedMatches
        {
            get { return ArrayList.ReadOnly(priorityApprovedMatches); }
        } private ArrayList priorityApprovedMatches;

        // Matches that have been ignored/committed and saved to the database. 
        public ArrayList CommitedMatches
        {
            get { return ArrayList.ReadOnly(commitedMatches); }
        } private ArrayList commitedMatches;

        #endregion
        
        #region Start/Stop

        void init()
        {
            lock (syncRoot)
            {
                threadCount = Options.Instance.GetIntOption("importthreadcount");
                if (threadCount < 1) //0 threads will take a very long time to complete :)
                    threadCount = 1;

                pendingMatches = ArrayList.Synchronized(new ArrayList());
                priorityPendingMatches = ArrayList.Synchronized(new ArrayList());
                matchesNeedingInput = ArrayList.Synchronized(new ArrayList());
                approvedMatches = ArrayList.Synchronized(new ArrayList());
                priorityApprovedMatches = ArrayList.Synchronized(new ArrayList());
                retrievingDetailsMatches = ArrayList.Synchronized(new ArrayList());
                commitedMatches = ArrayList.Synchronized(new ArrayList());

                importerThreads = new List<Thread>();
                allMatches = ArrayList.Synchronized(new ArrayList());
                lookupMatch = new Dictionary<int, RomMatch>();

                scraperProvider = new ScraperProvider(this);
                firstRun = true;
            }
        }

        public void Start()
        {
            lock (syncRoot)
            {
                doWork = true;
                pause = false;
                if (importerThreads.Count == 0)
                {
                    if (isBackground) //start retrieving immediately
                    {
                        Thread firstThread = new Thread(new ThreadStart(delegate()
                            {
                                getFilesToImport(); //first thread needs to locate games for import
                                scanRoms();
                            }
                            ));
                        firstThread.Name = "Importer Thread";
                        firstThread.Start();
                        importerThreads.Add(firstThread);

                        //start rest of threads
                        for (int x = 0; x < threadCount - 1; x++)
                        {
                            Thread thread = new Thread(new ThreadStart(scanRoms));
                            thread.Name = "Importer Thread";
                            thread.Start();
                            importerThreads.Add(thread);
                        }

                        importStarted = true;
                        if (RomStatusChanged != null)
                            RomStatusChanged(null, ImportAction.ImportStarted);
                    }
                    else //just get list of files to import
                    {
                        Thread firstThread = new Thread(new ThreadStart(getFilesToImport));
                        firstThread.Name = "Importer Thread";
                        importerThreads.Add(firstThread);
                        firstThread.Start();
                    }

                }
            }
        }

        //Only needs to be called if importer is not set to background,
        //only call after calling Start
        public void StartRetrieving()
        {
            if (isBackground || !importReady || importStarted)
                return;

            pause = false;

            if (importerThreads.Count == 1)
            {
                //thread has probably returned but just in case
                importerThreads[0].Join();
                importerThreads.Clear();
            }

            for (int x = 0; x < threadCount; x++)
            {
                Thread thread = new Thread(new ThreadStart(scanRoms));
                thread.Name = "Importer Thread";
                thread.Start();
                importerThreads.Add(thread);
            }

            importStarted = true;
            if (RomStatusChanged != null)
                RomStatusChanged(null, ImportAction.ImportStarted);
        }

        bool stopping = false;

        public void Stop()
        {
            if (stopping)
                return;

            lock (syncRoot)
            {
                stopping = true;

                pause = false;
                doWork = false;

                if (importerThreads.Count > 0)
                {
                    Logger.LogInfo("Shutting Down Importer Threads...");
                    //foreach (Thread currThread in importerThreads)
                        //currThread.Abort();

                    // wait for all threads to shut down
                    bool waiting = true;
                    while (waiting)
                    {
                        waiting = false;
                        foreach (Thread currThread in importerThreads)
                            waiting = waiting || currThread.IsAlive;
                        Thread.Sleep(100);
                    }

                    importerThreads.Clear();
                }

                if (Progress != null)
                    Progress(100, 0, 0, "Stopped");

                if (RomStatusChanged != null)
                    RomStatusChanged(null, ImportAction.ImportStopped);

                Logger.LogInfo("Stopped Importer");
                importStarted = false;
                importReady = false;

                stopping = false;
            }
        }

        public void Restart()
        {
            Stop();
            init();
            Start();
        }

        public void Pause()
        {
            pause = !pause;
        }

        #endregion

        #region Public Methods
        /// <summary>
        /// Adds a new RomMatch to the Importer, if the RomMatch is already being processed
        /// calling this will have no effect.
        /// </summary>
        /// <param name="romMatch"></param>
        public void Add(RomMatch romMatch)
        {
            lock (lookupSync)
            {
                if (lookupMatch.ContainsKey(romMatch.ID))
                    return; //Game already being processed
                else
                {
                    lookupMatch[romMatch.ID] = romMatch;
                    allMatches.Add(romMatch);
                }
            }

            romMatch.InfoChecked = false;
            romMatch.Ignore = false;
            romMatch.UserEdited = false;

            //user added so process first
            romMatch.HighPriority = true;

            if (RomStatusChanged != null)
                RomStatusChanged(romMatch, ImportAction.PendingFileAdded);

            lock (priorityPendingMatches.SyncRoot)
                priorityPendingMatches.Add(romMatch);

        }

        /// <summary>
        /// Resets RomMatch status and re-retrieves info
        /// </summary>
        /// <param name="romMatch"></param>
        public void ReProcess(RomMatch romMatch)
        {
            if (romMatch == null)
                return;

            romMatch.Game.IsInfoChecked = false;
            romMatch.Game.SaveInfoCheckedStatus();


            RemoveFromMatchLists(romMatch); 
            
            romMatch.InfoChecked = false;
            romMatch.Approved = false;
            romMatch.Commited = false;
            romMatch.GameDetails = null;
            romMatch.PossibleGameDetails = new ScraperResult[0];
            romMatch.Ignore = false;
            romMatch.HighPriority = true;
            romMatch.UserEdited = false;

            lock (lookupSync)
            {
                lookupMatch[romMatch.ID] = romMatch;
                allMatches.Add(romMatch);
            }

            lock (priorityPendingMatches.SyncRoot)
                priorityPendingMatches.Add(romMatch);

            if (RomStatusChanged != null)
                RomStatusChanged(romMatch, ImportAction.MatchReprocessed);
        }

        /// <summary>
        /// Approve a RomMatch requiring input and queue downloading selected info
        /// </summary>
        /// <param name="romMatch"></param>
        public void Approve(RomMatch romMatch)
        {
            if (romMatch == null)
                return;

            romMatch.Game.IsInfoChecked = false;
            romMatch.Game.SaveInfoCheckedStatus();

            RemoveFromMatchLists(romMatch);

            lock (lookupSync)
            {
                lookupMatch[romMatch.ID] = romMatch;
                allMatches.Add(romMatch);
            }

            romMatch.Approved = true;
            romMatch.Commited = false;
            romMatch.HighPriority = true;
            romMatch.UserEdited = false;

            lock (priorityApprovedMatches.SyncRoot)
            {
                priorityApprovedMatches.Add(romMatch);
            }

            if (RomStatusChanged != null)
                RomStatusChanged(romMatch, ImportAction.MatchApproved);
        }

        /// <summary>
        /// Removes the RomMatch from the Importer
        /// </summary>
        /// <param name="romMatch"></param>
        public void Ignore(RomMatch romMatch)
        {
            new Thread(new ThreadStart(delegate()
                {
                    romMatch.Ignore = true;
                    romMatch.InfoChecked = true;
                    romMatch.UserEdited = false;

                    RemoveFromMatchLists(romMatch);

                    romMatch.Game.IsInfoChecked = true;
                    romMatch.Game.SaveInfoCheckedStatus();

                    if (RomStatusChanged != null)
                        RomStatusChanged(romMatch, ImportAction.MatchIgnored);
                }
            )).Start();
        }

        public void Remove(int gameId)
        {
            RomMatch romMatch;
            lock (lookupSync)
            {
                if (!lookupMatch.ContainsKey(gameId))
                    return;
                romMatch = lookupMatch[gameId];
            }

            romMatch.Ignore = true;
            romMatch.InfoChecked = true;
            romMatch.UserEdited = false;

            RemoveFromMatchLists(romMatch);

            if (RomStatusChanged != null)
                RomStatusChanged(romMatch, ImportAction.MatchRemoved);
        }

        #endregion

        // The main loop that the import threads will run, checks for pending actions and updates status
        void scanRoms()
        {
            try
            {
                while (doWork)
                {
                    while (pause)
                    {
                        OnProgress("Paused");
                        if (!checkAndWait(100, 10, true))
                            return;
                    }

                    int previousCommittedCount = commitedMatches.Count;
                    int previousMatchesCount = allMatches.Count;

                    // if there is nothing to process, then sleep
                    while (pendingMatches.Count == 0 &&
                           approvedMatches.Count == 0 &&
                           priorityPendingMatches.Count == 0 &&
                           priorityApprovedMatches.Count == 0 &&
                           commitedMatches.Count == previousCommittedCount &&
                           previousMatchesCount == allMatches.Count &&
                           doWork
                           )
                    {
                        if (firstRun)
                        {
                            firstRun = false;
                            break;
                        }
                        if (!checkAndWait(100, 10, false))
                            return;
                    }

                    if (priorityPendingMatches.Count > 0)
                        processNextPendingMatch();
                    else if (priorityApprovedMatches.Count > 0)
                        processNextApprovedMatch();
                    else if (pendingMatches.Count > 0)
                        processNextPendingMatch();
                    else if (approvedMatches.Count > 0)
                        processNextApprovedMatch();

                    if (!doWork)
                        return;

                    UpdatePercentDone();

                    // if we are now just waiting on the user, say so
                    if (pendingMatches.Count == 0 && approvedMatches.Count == 0 &&
                        priorityPendingMatches.Count == 0 && priorityApprovedMatches.Count == 0 &&
                        matchesNeedingInput.Count > 0)
                    {
                        if (Progress != null)
                            Progress(percentDone, 0, matchesNeedingInput.Count, "Waiting for Approvals...");
                        if (RomStatusChanged != null)
                        {
                            RomStatusChanged(null, ImportAction.ImportWaiting);
                        }
                    }

                    // if we are now just waiting on the user, say so
                    if (pendingMatches.Count == 0 && approvedMatches.Count == 0 &&
                        priorityPendingMatches.Count == 0 && priorityApprovedMatches.Count == 0 &&
                        matchesNeedingInput.Count == 0)
                    {
                        percentDone = 0;

                        if (Progress != null)
                        {
                            UpdatePercentDone();
                            if (percentDone == 100)
                                Progress(100, 0, 0, "Done!");
                        }
                        if (commitedMatches.Count == allMatches.Count)
                        {
                            if (RomStatusChanged != null)
                            {
                                RomStatusChanged(null, ImportAction.ImportFinished);
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Logger.LogError("Unhandled error in Importer - {0}", e.Message);
            }
        }

        //Refreshes the DB and builds list of RomMatches based on Game.IsInfoChecked
        //called first when started
        void getFilesToImport()
        {
            try
            {
                if (Progress != null)
                    Progress(0, 0, 0, "Refreshing Database");

                List<RomMatch> localMatches = new List<RomMatch>();

                refreshDatabase();

                if (!doWork)
                    return;

                foreach (Game game in DB.Instance.GetGames())
                {
                    if (!doWork)
                        return;

                    if (!game.IsInfoChecked)
                    {
                        localMatches.Add(new RomMatch(game));
                    }
                }

                if (localMatches.Count > 0)
                {
                    for (int x = 0; x < localMatches.Count; x++)
                    {
                        lock (lookupSync)
                        {
                            if (!lookupMatch.ContainsKey(localMatches[x].ID))
                                lookupMatch[localMatches[x].ID] = localMatches[x];
                        }

                        lock (pendingMatches.SyncRoot)
                        {
                            pendingMatches.Add(localMatches[x]);
                            allMatches.Add(localMatches[x]);
                        }
                    }

                    importReady = true;
                    if (RomStatusChanged != null)
                        RomStatusChanged(localMatches, ImportAction.PendingFilesAdded);
                    if (Progress != null)
                        Progress(0, 0, 0, "Ready");
                }
                else //no files need importing
                {
                    importReady = true;
                    if (RomStatusChanged != null)
                        RomStatusChanged(null, ImportAction.NoFilesFound);
                    if (Progress != null)
                        Progress(0, 0, 0, "No new items found");
                }
            }
            catch (Exception e)
            {
                Logger.LogError("Unhandled error in Importer - {0} - {1}", e.Message, e.StackTrace);
            }
        }

        //Selects the next pending match and searches for possible game matches,
        //if a close match is found the RomMatch is added to the Approve queue else
        //it is added to the matches needing input queue
        private void processNextPendingMatch()
        {
            RomMatch romMatch = null;

            lock (priorityPendingMatches.SyncRoot) //prefer user inputted RomMatches
            {
                if (priorityPendingMatches.Count != 0)
                {
                    romMatch = (RomMatch)priorityPendingMatches[0];
                    priorityPendingMatches.Remove(romMatch);
                }
            }

            if (romMatch == null)
            {
                lock (pendingMatches.SyncRoot)
                {
                    if (pendingMatches.Count == 0)
                        return;

                    romMatch = (RomMatch)pendingMatches[0];
                    pendingMatches.Remove(romMatch);
                }
            }

            if (!doWork)
                return;

            if (Progress != null)
            {
                UpdatePercentDone();
                int processed = allMatches.Count - pendingMatches.Count;
                int total = allMatches.Count;
                Progress(percentDone, processed, total, "Retrieving matches: " + romMatch.Title);
            }

            //get possible matches and
            //update RomMatch
            getMatches(romMatch);

            if (!doWork)
                return;

            ImportAction action = ImportAction.MatchNeedsInput;
            if (romMatch.Approved) //close match found, add to approve list
            {
                if (romMatch.HighPriority)
                {
                    lock (priorityApprovedMatches.SyncRoot)
                    {
                        if (!romMatch.Ignore && romMatch.InfoChecked) //check RomMatch status within lock to ensure sync
                            priorityApprovedMatches.Add(romMatch);
                    }
                }
                else
                {
                    lock (approvedMatches.SyncRoot)
                        if (!romMatch.Ignore && romMatch.InfoChecked) //check RomMatch status within lock to ensure sync
                            approvedMatches.Add(romMatch);
                }
                action = ImportAction.MatchApproved;
            }
            else //close match not found, request user input
                lock (matchesNeedingInput.SyncRoot)
                    if (!romMatch.Ignore && romMatch.InfoChecked) //check RomMatch status within lock to ensure sync
                        matchesNeedingInput.Add(romMatch);

            if (!doWork)
                return;

            if (RomStatusChanged != null)
                RomStatusChanged(romMatch, action);
        }

        //Selects next Match from approved match lists.
        //Updates the Game with the specified Match details and commits
        void processNextApprovedMatch()
        {
            RomMatch romMatch = null;

            lock (priorityApprovedMatches.SyncRoot)
            {
                if (priorityApprovedMatches.Count != 0)
                {
                    romMatch = (RomMatch)priorityApprovedMatches[0];
                    priorityApprovedMatches.Remove(romMatch);
                }
            }

            if (romMatch == null)
            {
                lock (approvedMatches.SyncRoot)
                {
                    if (approvedMatches.Count == 0)
                        return;

                    romMatch = (RomMatch)approvedMatches[0];
                    approvedMatches.Remove(romMatch);
                }
            }

            if (!doWork)
                return;

            if (romMatch.Ignore || !romMatch.InfoChecked)
                return;

            OnProgress("Updating: " + romMatch.Title);

            //get info
            downloadInfo(romMatch);

            if (!doWork)
                return;

            //update and commit game
            commitGame(romMatch);


            if (!doWork)
                return;

            lock (commitedMatches.SyncRoot)
            {
                if (romMatch.Ignore || !romMatch.InfoChecked)
                    return;

                romMatch.Commited = true;
                commitedMatches.Add(romMatch);
            }

            if (RomStatusChanged != null)
                RomStatusChanged(romMatch, ImportAction.MatchCommited);
        }
        
        // removes the given match from all pending process lists
        private void RemoveFromMatchLists(RomMatch match)
        {
            lock (pendingMatches.SyncRoot)
            {
                if (pendingMatches.Contains(match))
                    pendingMatches.Remove(match);
            }

            lock (priorityPendingMatches.SyncRoot)
            {
                if (priorityPendingMatches.Contains(match))
                {
                    priorityPendingMatches.Remove(match);
                }
            }

            lock (matchesNeedingInput.SyncRoot)
            {
                if (matchesNeedingInput.Contains(match))
                    matchesNeedingInput.Remove(match);
            }

            lock (approvedMatches.SyncRoot)
            {
                if (approvedMatches.Contains(match))
                    approvedMatches.Remove(match);
            }

            lock (priorityApprovedMatches.SyncRoot)
            {
                if (priorityApprovedMatches.Contains(match))
                    priorityApprovedMatches.Remove(match);
            }

            lock (commitedMatches.SyncRoot)
            {
                if (commitedMatches.Contains(match))
                {
                    commitedMatches.Remove(match);
                }
            }

            lock (retrievingDetailsMatches.SyncRoot)
            {
                if (retrievingDetailsMatches.Contains(match))
                {
                    retrievingDetailsMatches.Remove(match);
                }
            }

            lock (lookupSync)
            {
                if (lookupMatch.ContainsKey(match.ID))
                {
                    lookupMatch.Remove(match.ID);
                    allMatches.Remove(match);
                }
            }
        }

        //retrieves possible Matches
        private void getMatches(RomMatch romMatch)
        {
            if (romMatch.InfoChecked || romMatch.Ignore)
                return;

            scraperProvider.GetMatches(romMatch);
        }

        void downloadInfo(RomMatch romMatch)
        {
            if (!romMatch.InfoChecked || romMatch.Ignore || romMatch.GameDetails == null)
                return;

            //retrieve selected info
            romMatch.GameDetails.DataProvider.DownloadInfo(romMatch);
        }

        //update and commit game
        void commitGame(RomMatch romMatch)
        {
            if (!romMatch.InfoChecked || romMatch.Ignore)
                return;
                        
            Game[] games = DB.Instance.GetGames("WHERE path='" + DB.Encode(romMatch.Game.Path) + "'");
            if (games.Length == 0) //rom deleted
                return;

            Game dbGame = games[0];
            
            ScraperGame details = romMatch.ScraperGame;
            if (details == null ||
                !doWork ||
                !romMatch.InfoChecked ||
                romMatch.Ignore
                )
                return;

            if (!string.IsNullOrEmpty(details.Title))
                dbGame.Title = details.Title;

            if (!string.IsNullOrEmpty(details.Company))
                dbGame.Company = details.Company;

            if (!string.IsNullOrEmpty(details.Description))
                dbGame.Description = details.Description;

            if (!string.IsNullOrEmpty(details.Year))
            {
                int year;
                if (!int.TryParse(details.Year, out year))
                    year = 0;
                dbGame.Yearmade = year;
            }

            if (!string.IsNullOrEmpty(details.Genre))
                dbGame.Genre = details.Genre;

            if (!string.IsNullOrEmpty(details.Grade))
            {
                double grade;
                if (!double.TryParse(details.Grade, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out grade))
                    grade = 0;

                while (grade > 10)
                {
                    grade = grade / 10;
                }

                dbGame.Grade = (int)Math.Round(grade);
            }

            if (!doWork)
                return;

            dbGame.IsInfoChecked = true;

            if (!romMatch.InfoChecked || romMatch.Ignore)
                return;

            dbGame.Save();

            using (ThumbGroup thumbs = new ThumbGroup(dbGame))
            {
                //dont't overwrite user selected thumbs and
                //periodically check doWork as dl'ing will take time
                if (!doWork || !romMatch.InfoChecked || romMatch.Ignore)
                    return;

                thumbs.FrontCover.Image = details.BoxFront;
                thumbs.SaveThumb(ThumbType.FrontCover); //downloads thumb here so will take time

                if (!doWork || !romMatch.InfoChecked || romMatch.Ignore)
                    return;

                thumbs.BackCover.Image = details.BoxBack;
                thumbs.SaveThumb(ThumbType.BackCover);

                if (!doWork || !romMatch.InfoChecked || romMatch.Ignore)
                    return;

                thumbs.TitleScreen.Image = details.TitleScreen;
                thumbs.SaveThumb(ThumbType.TitleScreen);

                if (!doWork || !romMatch.InfoChecked || romMatch.Ignore)
                    return;

                thumbs.InGame.Image = details.InGame;
                thumbs.SaveThumb(ThumbType.InGameScreen);

                //if (thumbs.FanartPath == "")
                //{
                //if (!doWork || !romMatch.InfoChecked || romMatch.Ignore)
                //return;

                //thumbs.FanartPath = details.ImageFanart;
                //thumbs.SaveThumb(ThumbType.Fanart);
                //}
            }

        }

        private void OnProgress(string message)
        {
            if (Progress != null)
            {
                UpdatePercentDone();
                int processed = commitedMatches.Count;
                int total = commitedMatches.Count + approvedMatches.Count + matchesNeedingInput.Count;
                Progress(percentDone, processed, total, message);
            }
        }

        private void UpdatePercentDone()
        {
            // calculate the total actions that need to be performed this session
            int mediaToScan = allMatches.Count;
            int mediaToCommit = allMatches.Count;
            int totalActions = mediaToScan + mediaToCommit;

            // if there is nothing to do, set progress to 100%
            if (totalActions == 0)
            {
                percentDone = 100;
                return;
            }

            // calculate the number of actions completed so far
            int mediaScanned = allMatches.Count - pendingMatches.Count - priorityPendingMatches.Count;
            int mediaCommitted = commitedMatches.Count;

            percentDone = (int)Math.Round(((double)mediaScanned + mediaCommitted) * 100 / ((double)totalActions));

            if (percentDone > 100)
                percentDone = 100;
        }


        internal void AddGames(Game[] games)
        {
            foreach (Game game in games)
            {


                if (importReady)
                {
                    RomMatch romMatch = new RomMatch(game) { HighPriority = true };
                    lock (lookupSync)
                    {
                        if (lookupMatch.ContainsKey(game.GameID))
                        {
                            romMatch = lookupMatch[game.GameID];
                            romMatch.InfoChecked = false;
                            romMatch.Ignore = false;
                            RemoveFromMatchLists(romMatch);
                        }

                        romMatch = new RomMatch(game);
                        romMatch.HighPriority = true;

                        game.IsInfoChecked = false;
                        game.SaveInfoCheckedStatus(); //update Game.IsInfoChecked to ensure game will be picked if importer stops before commiting
                        lookupMatch[game.GameID] = romMatch;
                        allMatches.Add(romMatch);
                    }
                    lock (priorityPendingMatches.SyncRoot)
                        priorityPendingMatches.Add(romMatch);

                    if (RomStatusChanged != null)
                        RomStatusChanged(romMatch, ImportAction.PendingFileAdded);
                }
                else
                {
                    game.IsInfoChecked = false;
                    game.SaveInfoCheckedStatus();
                }
            }
        }

        bool checkAndWait(int interval, int count, bool checkPause)
        {
            if (interval < 1 || count < 1)
                return true;

            for (int x = 0; x < count; x++)
            {
                if (!doWork)
                    return false;
                if (checkPause && !pause)
                    return true;
                Thread.Sleep(interval);
            }

            return true;
        }

        #region Refresh Database

        void refreshDatabase()
        {
            List<string> dbPaths = DB.Instance.GetAllGamePaths();

            if (!doWork)
                return;

            Emulator[] emus = DB.Instance.GetEmulators();

            if (!doWork)
                return;

            Dictionary<Emulator, string[]> allNewPaths = new Dictionary<Emulator, string[]>();
            int filesFound = 0;
            if (Progress != null)
                Progress(0, 0, 0, "Refreshing Database");

            //loop through each emu
            foreach (Emulator emu in emus)
            {
                if (!doWork)
                    return;

                string romDir = emu.PathToRoms;
                //check if rom dir exists
                if (!System.IO.Directory.Exists(romDir))
                {
                    Logger.LogError("{0} rom directory doesn not exist", emu.Title);
                    continue; //rom directory doesn't exist, skip
                }

                List<string> newPaths = new List<string>();

                //get list of files using each filter
                foreach (string filter in emu.Filter.Split(';'))
                {
                    if (!doWork)
                        return;

                    string[] gamePaths;
                    try
                    {
                        gamePaths = System.IO.Directory.GetFiles(romDir, filter, System.IO.SearchOption.AllDirectories); //get list of matches
                    }
                    catch
                    {
                        Logger.LogError("Error locating files in {0} rom directory using filter '{1}'", emu.Title, filter);
                        continue; //error with filter, skip
                    }

                    //loop through each new file
                    for (int x = 0; x < gamePaths.Length; x++)
                    {
                        if (!doWork)
                            return;

                        string path = gamePaths[x];
                        //check that path is not ignored, already in DB or not already picked up by a previous filter
                        if (!Options.Instance.ShouldIgnoreFile(path) && !dbPaths.Contains(path) && !newPaths.Contains(path)) 
                        {
                            newPaths.Add(path);
                            filesFound++;
                            if (Progress != null)
                                Progress(0, 0, filesFound, "Getting new items");
                        }
                    }
                }

                if (newPaths.Count > 0)
                    allNewPaths.Add(emu, newPaths.ToArray());
            }

            int filesAdded = 0;
           
            foreach (KeyValuePair<Emulator, string[]> val in allNewPaths)
            {
                //loop through each new file and commit
                foreach (string path in val.Value)
                {
                    if (!doWork)
                        return;

                    DB.Instance.AddGame(path, val.Key); //add new rom to DB
                    filesAdded++;
                    int perc = (int)Math.Round(((double)filesAdded / filesFound) * 100);
                    if (Progress != null)
                        Progress(perc, filesAdded, filesFound, "Adding new items");
                }
            }
        }

#endregion
    }

    public enum ImportAction
    {
        ImportFinished,
        ImportStarted,
        ImportStopped,
        ImportWaiting,

        MatchApproved,
        MatchCommited,
        MatchIgnored,
        MatchNeedsInput,
        MatchReprocessed,

        NoFilesFound,

        PendingFileAdded,
        PendingFilesAdded,
        MatchRemoved,
    }

    
}
